﻿package src.computation {
	
	/**
	* 
	* Reliability project
	* -----------------------
	* @author  Dmitrij Boycov
	* @mail    dmitry.boycov@gmail.com
	* @date    April 27, 2010
	* @version 1
	* 
	*/
	
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;	
	import flash.events.Event;
	//src
	import src.util.Utils;
	import src.entity.Node;
	import src.entity.Element;
	import src.computation.Stock;
	import src.common.MsgEvent;
	
	/**
	 * Abstract class content common methods and property for all computation methods.
	 */
	public class Method extends EventDispatcher{
	
		public static const ERROR:String = "ERROR";
		public static const STOP_COMPUTATION:String = "STOP_COMPUTATION";
		//	containers
		protected var elements:Dictionary;
		protected var nodes:Dictionary;
		protected var links:Dictionary;
		//
		protected var modified:Boolean;
		protected var time:Number = 0;
		protected var stock:Stock;
		
		public function Method(){		
		}
		
		public function setStock(dp) {
			this.stock = new Stock(dp);
			this.stock.setTimeSystemWork(this.time);
		}
		
		public function setContainers(elements:Dictionary, nodes:Dictionary, links:Dictionary) {
			if(nodes==null || Utils.sizeDict(nodes)==0 || elements == null || Utils.sizeDict(elements)== 0 || links==null || Utils.sizeDict(links)==0){
				return;
			}else{				
				this.elements = elements;
				this.links = links;
				this.nodes = nodes;				
			}
		}
		
		public function getContainers() {
			var d:Dictionary = new Dictionary();
			d["elements"] = this.elements;
			d["nodes"] = this.nodes;
			d["links"] = this.links;
			return d;
		}
		
		public function addNode():CNode {
			var node = new CNode();
			var id = 2;
			for (var key in this.nodes) {
				if (key > id) id = key;
			}
			id++;
			node.id = id;
			node.name = "This node were added in during scheme transformation.";
			this.nodes[id] = node;
			return node;
		}
		
		public function setTime(time:Number) {
			this.time = time;
			if(this.stock!=null){
				this.stock.setTimeSystemWork(this.time);
			}
		}
		
		protected function printDict(dict:Dictionary) {
			var i;
			if(dict!=null){
				for each(i in dict) {					
					i.print();				
				}
			}
		} 
		
		
		/**
		 * deleteFreeElements()
		 * If there is an element having no links that should be removed.
		 */
		protected function deleteFreeElements() {
			if (this.elements != null) {				
				for (var key in this.elements){
					if (this.elements[key].inLink == null && this.elements[key].outLink == null) {
						delete this.elements[key];
					}					
				}
			}
			if (this.links != null) {
				for (var key in this.links){
					if (this.links[key].end == null && this.links[key].start == null) {
						delete this.links[key];
					}					
				}
			}
			if (this.nodes != null) {
				for (var key in this.nodes){
					if (this.nodes[key].links == null) {
						delete this.nodes[key];
					}					
				}
			}
		}
		
		/**
		 * isNextElement
		 * 		+---+		 +---+
		 * 		|	|-- ? ---|	 | 
		 * 		+---+		 +---+
		 * @param	idLink
		 * @return	Boolean
		 */
		protected function isNextElement(idLink):Boolean {
			var l:CLink = this.links[idLink];
			if (l != null) {
				if (l.start != null && l.end != null) {					
					if ( l.start["type"] == Element.TYPE && l.end["type"] == Element.TYPE) {
						return true;
						}else {
							return false;
						}
				}else {
					return false;
				}
			}else {
				return false;
			}
		}
		
		/**
		 *  removeNodeBetweenElements
		 *      +---+	  +---+ 	    +---+	+---+
		 * 	   -|   |--o--|   |-   =>  -|   |---|   |-
		 * 		+---+	  +---+		    +---+	+---+
		 * 
		 * @param	idLink
		 */		
		public function removeNodeBetweenElements(idLink) {
			var l:CLink = this.links[idLink];
			if (l != null) {
				if (l.start != null && l.end != null) {
					var node:CNode = null;
					if (l.start["type"] == Node.TYPE && l.end["type"] == Element.TYPE) {
						node = this.nodes[l.start["id"]];
					}
					if (l.end["type"] == Node.TYPE && l.start["type"] == Element.TYPE) {
						node = this.nodes[l.end["id"]];
					}
					if (node == null) return false;
					var oneLink:CLink;
					var twoLink:CLink;
					var l1;
					var i = 1;
					for each(l1 in node.links) {
						if (i == 1) {
							oneLink = this.links[l1];
						}
						if (i == 2) {
							twoLink = this.links[l1];
						}
						i++;
					}
					var dEl = oneLink.getOtherEnd(node);
					var el = this.elements[dEl["id"]];
					oneLink.removeEnd(node);
					oneLink.removeEnd(el);
					delete this.links[oneLink.id];
					twoLink.removeEnd(node);					
					delete this.nodes[node.id];
					twoLink.addEnd(el);
					el.addLink(twoLink, this.links);
				}
			}	
		}		
		
		/**
		 * isElNodeEl
		 *      +---+	  +---+ 
		 * 	   -|   |--o--|   |-   => ?
		 * 		+---+	  +---+	
		 * @param	idLink
		 * @return Boolean
		 */
		protected function isElNodeEl(idLink):Boolean {
			var l:CLink = this.links[idLink];
			if (l != null && l.id!=1 && l.id!=2) {   //except "in" and "out" node
				if (l.start != null && l.end != null) {
					var node:CNode = null;
					if (l.start["type"] == Node.TYPE && l.end["type"] == Element.TYPE) {
						node = this.nodes[l.start["id"]];
					}
					if (l.end["type"] == Node.TYPE && l.start["type"] == Element.TYPE) {
						node = this.nodes[l.end["id"]];
					}
					if (node == null || node.id==1 || node.id==2) return false;
					if (Utils.sizeDict(node.links) != 2) return false;
					var oneLink:CLink;
					var twoLink:CLink;
					var l1;
					var i = 1;
					for each(l1 in node.links) {
						if (i == 1) {
							oneLink = this.links[l1];
						}
						if (i == 2) {
							twoLink = this.links[l1];
						}
						i++;
					}
					if ((oneLink.end["type"] == Element.TYPE || oneLink.start["type"] == Element.TYPE) 
						&& (twoLink.start["type"] == Element.TYPE || twoLink.end["type"] == Element.TYPE)) {
						return true;
					}					
				}else {
					return false;
				}
			}else {
				return false;
			}
			return false;
		}
		
		protected function transformTriangle(e1:CElement, e2:CElement, e3:CElement, u1:CNode, u2:CNode, u3:CNode) {}
		
		/**
		 *  reduceTriangle
		 *			|
		 * 	  +-----0-----+	
		 * 	  |		u1	  |
		 * 	+---+		+---+
		 * 	|e1 |		|e3 |
		 * 	+---+		+---+
		 * 	  |	  +---+	  |
		 * u2 0---|e2 |---0 u3
		 * 	  |	  +---+	  |
		 */
		protected function reduceTriangle() {			
			if (this.nodes == null) return;
			for each(var u1:CNode in this.nodes) {
				var u2 = null;
				var u3 = null;
				var e1 = null;
				var e2 = null;
				var e3 = null;
				var nodeLinks = u1.links;
				if ((Utils.sizeDict(nodeLinks) > 2) || ((u1.id == 1 || u1.id==2) && Utils.sizeDict(nodeLinks))) {
					// find e1
					for each(var itL in nodeLinks) {						
						var nodeL = this.links[itL];						
						e1 = nodeL.getOtherEnd(u1);
						if (e1 != null && e1.type == Element.TYPE) {							
							e1 = this.elements[e1.id];
							if (e1.inLink != null && e1.inLink == nodeL.id && e1.outLink != null) {								
								u2 = this.links[e1.outLink].getOtherEnd(e1);
							}else if (e1.outLink != null && e1.outLink == nodeL.id && e1.inLink != null) {								
								u2 = this.links[e1.inLink].getOtherEnd(e1);
							}							
							if (u2!=null && u2.type == Node.TYPE) {
								u2 = this.nodes[u2.id];
								var u2Links = u2.links;							
								if (Utils.sizeDict(u2Links) > 2) {
									for each(var u2it in u2Links) {										
										var u2Link = this.links[u2it];										
										var e2 = u2Link.getOtherEnd(u2);
										if (e2 != null && e2.type == Element.TYPE) {
											e2 = this.elements[e2.id];
											if (e2.inLink != null && e2.inLink == u2Link.id && e2.outLink != null) {
												u3 = this.links[e2.outLink].getOtherEnd(e2);
												if (u3 != null && u3.type == Node.TYPE) {
													u3 = this.nodes[u3.id];
													var u3Links = u3.links;
													if (Utils.sizeDict(u3Links) > 2) {
														//
														for each(var u3it in u3Links) {
															var u3Link = this.links[u3it];
															e3 = u3Link.getOtherEnd(u3);
															if (e3 != null && e3.type == Element.TYPE) {
																e3 = this.elements[e3.id];
																if (e3.inLink != null && e3.inLink == u3Link.id && e3.outLink != null) {
																	var uq = this.links[e3.outLink].getOtherEnd(e3);
																	if (uq != null && uq.id == u1.id) {
																		// it is find!!!
																		transformTriangle(e1, e2, e3, u1, u2, u3);
																	}
																}else if(e3.outLink != null && e3.outLink == u3Link.id && e3.inLink != null) {
																			var uq = this.links[e3.inLink].getOtherEnd(e3);
																			if (uq != null && uq.id == u1.id) {
																				// it is find!!!
																				transformTriangle(e1, e2, e3, u1, u2, u3);
																			}
																		}
															}
														}
													}
												}
											}
										}
									}
								}
							} 
						}
					} 
				}
			}			
		}
		
		/**
		 * initElements()
		 * if element have type then params of elements set by params type
		 */
		protected function initElements() {
			if(this.stock!=null){
				for each(var el:CElement in this.elements) {
					if (el.stockId > 0) {
						var obj = this.stock.getItemAt(el.stockId);
						el.setParamsByStock(obj);
					}
				}
			}
		}
		
		/**
 		 * removeLinksBetweenNodes
		 * -o------o- => -o-
 		 * @param	id - id of node
 		 * @return
 		 */
		protected function removeLinksBetweenNodes(id:Number):Boolean {			
			var inNode:CNode = this.nodes[id];			
			var nodeLinks:Dictionary  = inNode.links;
			var l;
			var idL;
			// check all links of inNode			
			for each(idL in nodeLinks) {
				var b = null;
				l = this.links[idL];
				var idB = l.getOtherEnd(inNode);				
				if ( idB != null) {
					if(idB["type"]==Node.TYPE){
						b = this.nodes[idB["id"]];
					}
					if(idB["type"] == Element.TYPE){
						b = this.elements[idB["id"]];
					}
				}							
				if (b == null) {
					dispatchEvent(new MsgEvent(Method.ERROR, "Есть разрыв в схеме4."));
					return false;
				}
				// delete links node-node
				if (b.type == Node.TYPE && b.id!=1 && b.id!=2) { // except "in" and "out" node					
					var bLinks = b.links; // id of links
					var bl;
					// replace links
					for each(bl in bLinks) {
						var bLink:CLink = this.links[bl];						
						bLink.removeEnd(b);
						if (inNode.addLink(bLink,this.links)) {							
							//************
						}else { // dublicate links, because this link need delete
							bLink.unlinkElements(this.elements);
							bLink.unlinkNode(this.nodes);
							delete this.links[bl];
							bLink = null;
						}						
					}
					delete this.nodes[b.id];
				}			
			}
			return true;
		}	
		
		/**
		 * removeUnnecessaryNodes
		 *        +- 	   +-	
		 * -0-----0-  =>  -0-
		 * 	      +-	   +-
		 * 
		 * and delete  			+---+
		 * such elements  	0---|   |-empty	
		 * 			   		 	+---+	
		 */       
		protected function removeUnnecessaryNodes() {
			var node:CNode;
			// remove unnecessary nodes
			for each(node in this.nodes) {
				if(node.id){
					if (!this.removeLinksBetweenNodes(node.id)) {
						// computation error
						dispatchEvent(new MsgEvent(Method.ERROR, "Ошибка вычислений."));
						return false;
					}
				}
			}
			for each(var el:CElement in this.elements) {
				var idLink = null;
				if (el.inLink == null && el.outLink != null) {
					idLink = el.outLink;
				}else if (el.inLink != null && el.outLink == null) {
					idLink = el.inLink;
				}else if (el.inLink == null && el.outLink == null) {
					delete this.elements[el.id];
				}
				if(idLink!=null){
					var l:CLink = this.links[idLink];
					var u = l.getOtherEnd(el);
					u = this.nodes[u.id];
					u.unLink(l.id);
					delete this.links[l.id];
					delete this.elements[el.id];
				}
			}
		}
		
		/**
		 * unitSeriesElement
		 * This method are need redifine into descendent
		 * @param	idBetweenLink
		 */
		protected function unitSeriesElement(idBetweenLink) {}
		
		protected function reduceSeriesElemnts() {
			trace("reduceSeriesElemnts()");
			var el:CElement;			
			for each(el in this.elements) {
				var nextEl:CElement = null;
				var betwenLink:CLink = null;
				if (!el.inLink || !el.outLink) {				
					dispatchEvent(new MsgEvent(Method.ERROR, "Есть разрыв в схеме."));
					return false;
				}
				if (this.isElNodeEl(el.inLink)) {
					this.removeNodeBetweenElements(el.inLink);
				}
				if (this.isElNodeEl(el.outLink)) {
					this.removeNodeBetweenElements(el.outLink);
				}
				//				
				if (this.isNextElement(el.inLink)) {					
					this.unitSeriesElement(el.inLink);
					//
					this.modified = true;
				}
				if (this.isNextElement(el.outLink)) {					
					this.unitSeriesElement(el.outLink);
					//
					this.modified = true;
				}	
			}
			this.printDict(this.elements);
		}		
		
		/**
		 * startComputation
		 * This method are need redifine into descendent
		 */
		public function startComputation() {}
		
		/**
		 * getResult()
		 * return result of computation
		 * This method are need redifine into descendent
		 */
		public function getResult() {
			return null;
		}
		
		/**
		 *  getParamForm()
		 *  return form with specific params for method
		 */
		public function getParamForm() {
			return null;
		}
		
		public function getResForm() {
			return null;
		}
		
	}
	
}